/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;

using Microsoft.Scripting.Generation;
using Microsoft.Scripting.Utils;

namespace Microsoft.Scripting.Ast {
    partial class LambdaCompiler {
        /// <summary>
        /// Generates code for this expression in a value position.
        /// This method will leave the value of the expression
        /// on the top of the stack typed as Type.
        /// </summary>
        internal void EmitExpression(Expression node) {
            EmitExpression(node, true);
        }
        
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
        private void EmitExpression(Expression node, bool emitDebugMarkers) {
            Debug.Assert(node != null);

            if (node.IsDynamic) {
                throw new InvalidOperationException("ActionExpression not reduced");
            }

            bool startEmitted = emitDebugMarkers && EmitExpressionStart(node);

            int kind = (int)node.NodeType;
            Debug.Assert(kind < _Emitters.Length);

            _Emitters[kind](this, node);

            if (startEmitted) {
                EmitExpressionEnd();
            }
        }

        /// <summary>
        /// Emits an expression and discards the result.  For some nodes this emits
        /// more optimial code then EmitExpression/Pop
        /// </summary>
        private void EmitExpressionAsVoid(Expression node) {
            Debug.Assert(node != null);

            bool startEmitted = EmitExpressionStart(node);

            switch (node.NodeType) {
                case ExpressionType.Assign:
                    Emit((AssignmentExpression)node, EmitAs.Void);
                    break;
                case ExpressionType.Block:
                    Emit((Block)node, EmitAs.Void);
                    break;
                default:
                    EmitExpression(node, false);
                    if (node.Type != typeof(void)) {
                        _ilg.Emit(OpCodes.Pop);
                    }
                    break;
            }
            if (startEmitted) {
                EmitExpressionEnd();
            }
        }

        /// <summary>
        /// Generates the code for the expression, leaving it on
        /// the stack typed as object.
        /// </summary>
        private void EmitExpressionAsObject(Expression node) {
            EmitExpression(node);
            EmitBoxing(node.Type);
        }

        #region DebugMarkers

        private bool EmitExpressionStart(Expression node) {
            if (!_emitDebugSymbols) {
                return false;
            }

            Annotations annotations = node.Annotations;
            SourceSpan span;
            SourceLocation header;

            if (annotations.TryGet<SourceSpan>(out span)) {
                if (annotations.TryGet<SourceLocation>(out header)) {
                    EmitPosition(span.Start, header);
                } else {
                    EmitPosition(span.Start, span.End);
                }
                return true;
            }
            return false;
        }

        private void EmitExpressionEnd() {
            EmitSequencePointNone();
        }

        #endregion
        
        #region BinaryExpression

        private static void EmitBinaryExpression(LambdaCompiler lc, Expression expr) {
            BinaryExpression node = (BinaryExpression)expr;

            Debug.Assert(node.NodeType != ExpressionType.AndAlso && node.NodeType != ExpressionType.OrElse);

            if (NullableVsNull(node.Left, node.Right)) {
                lc.EmitExpressionAddress(node.Left, node.Left.Type);

                lc.GenerateNullableBinaryOperator(node.NodeType, node.Left.Type);
            } else if (NullableVsNull(node.Right, node.Left)) {
                // null vs Nullable<T>
                lc.EmitExpressionAddress(node.Right, node.Right.Type);

                lc.GenerateNullableBinaryOperator(node.NodeType, node.Right.Type);
            } else {
                lc.EmitExpression(node.Left);
                lc.EmitExpression(node.Right);

                if (node.Method != null) {
                    lc.IL.EmitCall(node.Method);
                } else {
                    lc.GenerateBinaryOperator(node.NodeType, node.Type);
                }
            }
        }

        private void GenerateNullableBinaryOperator(ExpressionType astNodeType, Type nullableType) {
            switch (astNodeType) {
                case ExpressionType.NotEqual:
                    _ilg.EmitPropertyGet(nullableType, "HasValue");
                    break;
                case ExpressionType.Equal:
                    _ilg.EmitPropertyGet(nullableType, "HasValue");
                    _ilg.EmitBoolean(false);
                    _ilg.Emit(OpCodes.Ceq);
                    break;
                default:
                    throw new InvalidOperationException(astNodeType.ToString());
            }
        }

        private static bool NullableVsNull(Expression nullable, Expression nullVal) {
            return TypeUtils.IsNullableType(nullable.Type) && ConstantCheck.IsConstant(nullVal, null);
        }

        private static void EmitAndAlsoBinaryExpression(LambdaCompiler lc, Expression node) {
            lc.EmitBooleanOperator((BinaryExpression)node, true);
        }

        private static void EmitOrElseBinaryExpression(LambdaCompiler lc, Expression node) {
            lc.EmitBooleanOperator((BinaryExpression)node, false);
        }

        private void EmitBooleanOperator(BinaryExpression node, bool isAnd) {
            Label otherwise = _ilg.DefineLabel();
            Label endif = _ilg.DefineLabel();

            // if (_left) 
            EmitBranchFalse(node.Left, otherwise);
            // then

            if (isAnd) {
                EmitExpression(node.Right);
            } else {
                _ilg.EmitInt(1);
            }

            _ilg.Emit(OpCodes.Br, endif);
            // otherwise
            _ilg.MarkLabel(otherwise);

            if (isAnd) {
                _ilg.EmitInt(0);
            } else {
                EmitExpression(node.Right);
            }

            // endif
            _ilg.MarkLabel(endif);
            return;
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
        private void GenerateBinaryOperator(ExpressionType nodeType, Type type) {
            switch (nodeType) {
                case ExpressionType.ArrayIndex:
                    _ilg.EmitLoadElement(type);
                    break;

                case ExpressionType.Equal:
                    _ilg.Emit(OpCodes.Ceq);
                    break;

                case ExpressionType.NotEqual:
                    _ilg.Emit(OpCodes.Ceq);
                    _ilg.EmitInt(0);
                    _ilg.Emit(OpCodes.Ceq);
                    break;

                case ExpressionType.GreaterThan:
                    _ilg.Emit(OpCodes.Cgt);
                    break;

                case ExpressionType.LessThan:
                    _ilg.Emit(OpCodes.Clt);
                    break;

                case ExpressionType.GreaterThanOrEqual:
                    _ilg.Emit(OpCodes.Clt);
                    _ilg.EmitInt(0);
                    _ilg.Emit(OpCodes.Ceq);
                    break;

                case ExpressionType.LessThanOrEqual:
                    _ilg.Emit(OpCodes.Cgt);
                    _ilg.EmitInt(0);
                    _ilg.Emit(OpCodes.Ceq);
                    break;
                case ExpressionType.Multiply:
                    _ilg.Emit(OpCodes.Mul);
                    break;
                case ExpressionType.Modulo:
                    _ilg.Emit(OpCodes.Rem);
                    break;
                case ExpressionType.Add:
                    _ilg.Emit(OpCodes.Add);
                    break;
                case ExpressionType.Subtract:
                    _ilg.Emit(OpCodes.Sub);
                    break;
                case ExpressionType.Divide:
                    _ilg.Emit(OpCodes.Div);
                    break;
                case ExpressionType.LeftShift:
                    _ilg.Emit(OpCodes.Shl);
                    break;
                case ExpressionType.RightShift:
                    _ilg.Emit(OpCodes.Shr);
                    break;
                case ExpressionType.And:
                    _ilg.Emit(OpCodes.And);
                    break;
                case ExpressionType.Or:
                    _ilg.Emit(OpCodes.Or);
                    break;
                case ExpressionType.ExclusiveOr:
                    _ilg.Emit(OpCodes.Xor);
                    break;
                //TODO: implement this
                //case ExpressionType.AddChecked:
                //case ExpressionType.MultiplyChecked:
                //case ExpressionType.SubtractChecked:
                //case ExpressionType.Power:
                //case ExpressionType.Coalesce:

                default:
                    throw new InvalidOperationException(nodeType.ToString());
            }
        }

        #endregion

        #region InvocationExpression

        private static void EmitInvocationExpression(LambdaCompiler lc, Expression expr) {
            InvocationExpression node = (InvocationExpression)expr;

            // TODO: need a smarter implementation here
            // (inlining, support quoted lambdas, etc)
            // see ExpressionCompiler in Linq

            lc.EmitExpression(Expression.Call(node.Expression, node.Expression.Type.GetMethod("Invoke"), node.Arguments));
        }

        #endregion

        #region MethodCallExpression

        private static void EmitMethodCallExpression(LambdaCompiler lc, Expression expr) {
            MethodCallExpression node = (MethodCallExpression)expr;

            // Emit instance, if calling an instance method

            if (!node.Method.IsStatic) {
                Type type = node.Method.DeclaringType;

                if (type.IsValueType) {
                    lc.EmitAddress(node.Object, type);
                } else {
                    lc.EmitExpression(node.Object);
                }
            }

            ParameterInfo[] parameterInfos = node.Method.GetParameters();

            // Emit arguments
            Debug.Assert(node.Arguments.Count == parameterInfos.Length);
            for (int arg = 0; arg < parameterInfos.Length; arg++) {
                Expression argument = node.Arguments[arg];
                Type type = parameterInfos[arg].ParameterType;
                lc.EmitArgument(argument, type);
            }

            // Emit the actual call
            lc._ilg.EmitCall(node.Method);
        }

        private void EmitArgument(Expression argument, Type type) {
            if (type.IsByRef) {
                EmitAddress(argument, type.GetElementType());
            } else {
                EmitExpression(argument);
            }
        }

        #endregion

        /// <summary>
        /// Expression is significant if:
        ///   * it is not an empty expression
        /// == or ==
        ///   * it is an empty expression, and 
        ///   * it has a valid span, and
        ///   * we are emitting debug symbols
        /// </summary>
        private bool Significant(Expression node) {
            if (node.NodeType != ExpressionType.EmptyStatement) {
                // non-empty expression is significant
                return true;
            }

            if (_emitDebugSymbols) {
                SourceSpan span = node.Annotations.Get<SourceSpan>();
                if (span.IsValid) {
                    return true;
                }
                SourceLocation header = node.Annotations.Get<SourceLocation>();
                if (header.IsValid) {
                    return true;
                }
            }

            // Not a significant expression
            return false;
        }

        private static void EmitConditionalExpression(LambdaCompiler lc, Expression expr) {
            ConditionalExpression node = (ConditionalExpression)expr;

            Label next = lc._ilg.DefineLabel();
            lc.EmitBranchFalse(node.Test, next);
            lc.EmitExpression(node.IfTrue);

            if (lc.Significant(node.IfFalse)) {
                Label eoi = lc._ilg.DefineLabel();
                lc._ilg.Emit(OpCodes.Br, eoi);
                lc._ilg.MarkLabel(next);
                lc.EmitExpression(node.IfFalse);
                lc._ilg.MarkLabel(eoi);
            } else {
                lc._ilg.MarkLabel(next);
            }
        }

        /// <summary>
        /// The main entry to the constant emitting.
        /// This will handle runtime constants.
        /// Constants will be left on the execution stack as their direct visible type.
        /// </summary>
        private static void EmitConstantExpression(LambdaCompiler lc, Expression expr) {
            ConstantExpression node = (ConstantExpression)expr;

            Type type;
            object value = node.Value;
            if (value is SymbolId) {
                lc._ilg.EmitSymbolId((SymbolId)value);
            } else if ((type = value as Type) != null) {
                lc.EmitType(type);
            } else if (!lc._ilg.TryEmitConstant(value)) {
                lc.EmitRuntimeConstant(node);
            }
        }

        private void EmitType(Type type) {
            if (!(type is TypeBuilder) && !type.IsGenericParameter && !type.IsVisible) {
                // can't ldtoken on a non-visible type, refer to it via a runtime constant...
                EmitRuntimeConstant(Expression.Constant(type));
            } else {
                _ilg.EmitType(type);
            }
        }

        private void EmitRuntimeConstant(ConstantExpression node) {
            if (_constantPoolCache == null) {
                _constantPoolCache = new Dictionary<object, Slot>(ReferenceEqualityComparer<object>.Instance);
            }

            object value = node.Value;
            Slot slot;
            if (!_constantPoolCache.TryGetValue(value, out slot)) {
                slot = _constantPool.AddData(value, node.Type);
                _constantPoolCache.Add(value, slot);
            }

            slot.EmitGet(_ilg);
        }

        private static void EmitUnaryExpression(LambdaCompiler lc, Expression expr) {
            UnaryExpression node = (UnaryExpression)expr;

            if (node.NodeType == ExpressionType.Convert && node.Type == typeof(void)) {
                lc.EmitExpressionAsVoid(node.Operand);
                return;
            }

            lc.EmitExpression(node.Operand);

            switch (node.NodeType) {
                case ExpressionType.Convert:
                    lc.EmitCast(node.Operand.Type, node.Type);
                    break;

                case ExpressionType.Not:
                    if (node.Operand.Type == typeof(bool)) {
                        lc._ilg.Emit(OpCodes.Ldc_I4_0);
                        lc._ilg.Emit(OpCodes.Ceq);
                    } else {
                        lc._ilg.Emit(OpCodes.Not);
                    }
                    break;
                case ExpressionType.Negate:
                    lc._ilg.Emit(OpCodes.Neg);
                    break;
                case ExpressionType.OnesComplement:
                    lc._ilg.Emit(OpCodes.Not);
                    break;
                //TODO: implement this
                //case ExpressionType.ArrayLength:
                //case ExpressionType.TypeAs:
                //case ExpressionType.NegateChecked:
                //case ExpressionType.ConvertChecked:
                //case ExpressionType.UnaryPlus:
                //case ExpressionType.Quote:

                default:
                    throw new NotImplementedException();
            }
        }

        private static void EmitNewExpression(LambdaCompiler lc, Expression expr) {
            NewExpression node = (NewExpression)expr;
            ReadOnlyCollection<Expression> arguments = node.Arguments;
            for (int i = 0; i < arguments.Count; i++) {
                lc.EmitExpression(arguments[i]);
            }
            if (node.Constructor != null) {
                lc._ilg.EmitNew(node.Constructor);
            } else {
                Debug.Assert(arguments.Count == 0, "Node with arguments must have a constructor.");
                Debug.Assert(node.Type.IsValueType, "Only value type may have constructor not set.");

                Slot temp = lc._ilg.GetLocalTmp(node.Type);
                temp.EmitGetAddr(lc._ilg);
                lc._ilg.Emit(OpCodes.Initobj, node.Type);
                temp.EmitGet(lc._ilg);
                lc._ilg.FreeLocalTmp(temp);
            }
        }

        private static void EmitTypeBinaryExpression(LambdaCompiler lc, Expression expr) {
            TypeBinaryExpression node = (TypeBinaryExpression)expr;

            if (node.TypeOperand.IsAssignableFrom(node.Expression.Type)) {
                // if its always true just emit the bool
                lc._ilg.EmitBoolean(true);
                return;
            }

            lc.EmitExpressionAsObject(node.Expression);
            lc._ilg.Emit(OpCodes.Isinst, node.TypeOperand);
            lc._ilg.Emit(OpCodes.Ldnull);
            lc._ilg.Emit(OpCodes.Cgt_Un);
        }

        private void EmitArrayIndexAssignment(AssignmentExpression node) {
            BinaryExpression arrayIndex = (BinaryExpression)node.Expression;

            EmitExpression(node.Value);

            // Save the expression value - order of evaluation is different than that of the Stelem* instruction
            Slot temp = _ilg.GetLocalTmp(node.Type);
            temp.EmitSet(_ilg);

            // Emit the array reference
            EmitExpression(arrayIndex.Left);
            // Emit the index (integer)
            EmitExpression(arrayIndex.Right);
            // Emit the value
            temp.EmitGet(_ilg);
            // Store it in the array
            _ilg.EmitStoreElement(node.Type);
            temp.EmitGet(_ilg);
            _ilg.FreeLocalTmp(temp);
        }

        private void EmitVariableAssignment(AssignmentExpression node, EmitAs emitAs) {
            Slot slot = GetVariable(node.Expression);

            if (TypeUtils.IsNullableType(node.Type)) {
                // Nullable<T> being assigned...
                if (ConstantCheck.IsConstant(node.Value, null)) {
                    slot.EmitGetAddr(_ilg);
                    _ilg.Emit(OpCodes.Initobj, node.Type);
                    if (emitAs != EmitAs.Void) {
                        slot.EmitGet(_ilg);
                    }
                    return;
                } else if (node.Type != node.Value.Type) {
                    throw new InvalidOperationException();
                }
                // fall through & emit the store from Nullable<T> -> Nullable<T>
            }
            EmitExpression(node.Value);
            if (emitAs != EmitAs.Void) {
                _ilg.Emit(OpCodes.Dup);
            }
            slot.EmitSet(_ilg);
        }

        private static void EmitAssignmentExpression(LambdaCompiler lc, Expression expr) {
            lc.Emit((AssignmentExpression)expr, EmitAs.Default);
        }

        private void Emit(AssignmentExpression node, EmitAs emitAs) {
            switch (node.Expression.NodeType) {
                case ExpressionType.ArrayIndex:
                    EmitArrayIndexAssignment(node);
                    if (emitAs == EmitAs.Void) {
                        _ilg.Emit(OpCodes.Pop);
                    }
                    return;
                case ExpressionType.MemberAccess:
                    EmitMemberAssignment(node);
                    if (emitAs == EmitAs.Void) {
                        _ilg.Emit(OpCodes.Pop);
                    }
                    return;
                case ExpressionType.Parameter:
                case ExpressionType.Variable:
                    EmitVariableAssignment(node, emitAs);
                    return;
                default:
                    throw new InvalidOperationException("Invalid lvalue for assignment: " + node.Expression.NodeType);
            }
        }

        private static void EmitVariableExpression(LambdaCompiler lc, Expression expr) {
            lc.GetVariable(expr).EmitGet(lc._ilg);
        }

        private static void EmitParameterExpression(LambdaCompiler lc, Expression expr) {
            lc.GetVariable(expr).EmitGet(lc._ilg);
        }

        private static void EmitLambdaExpression(LambdaCompiler lc, Expression expr) {
            LambdaExpression node = (LambdaExpression)expr;
            lc.EmitDelegateConstruction(node, node.Type);
        }

        private static void EmitLocalScopeExpression(LambdaCompiler lc, Expression expr) {
            LocalScopeExpression node = (LocalScopeExpression)expr;
            lc._scope.EmitVariableAccess(lc, node.Variables);
        }

        private void EmitMemberAssignment(AssignmentExpression node) {
            MemberExpression lvalue = (MemberExpression)node.Expression;

            // emit "this", if any
            EmitInstance(lvalue.Expression, lvalue.Member.DeclaringType);

            // emit value
            EmitExpression(node.Value);

            // save the value so we can return it
            _ilg.Emit(OpCodes.Dup);
            Slot temp = _ilg.GetLocalTmp(node.Type);
            temp.EmitSet(_ilg);

            switch (lvalue.Member.MemberType) {
                case MemberTypes.Field:
                    _ilg.EmitFieldSet((FieldInfo)lvalue.Member);
                    break;
                case MemberTypes.Property:
                    _ilg.EmitPropertySet((PropertyInfo)lvalue.Member);
                    break;
                default:
                    throw new InvalidOperationException("Invalid member type: " + lvalue.Member.MemberType);
            }

            temp.EmitGet(_ilg);
            _ilg.FreeLocalTmp(temp);
        }

        private static void EmitMemberExpression(LambdaCompiler lc, Expression expr) {
            MemberExpression node = (MemberExpression)expr;

            // emit "this", if any
            lc.EmitInstance(node.Expression, node.Member.DeclaringType);

            switch (node.Member.MemberType) {
                case MemberTypes.Field:
                    lc._ilg.EmitFieldGet((FieldInfo)node.Member);
                    break;
                case MemberTypes.Property:
                    lc._ilg.EmitPropertyGet((PropertyInfo)node.Member);
                    break;
                default:
                    Debug.Assert(false, "Invalid member type");
                    break;
            }
        }

        private void EmitInstance(Expression instance, Type type) {
            if (instance != null) {
                if (type.IsValueType) {
                    EmitAddress(instance, type);
                } else {
                    EmitExpression(instance);
                }
            }
        }

        private static void EmitNewArrayExpression(LambdaCompiler lc, Expression expr) {
            NewArrayExpression node = (NewArrayExpression)expr;

            if (node.NodeType == ExpressionType.NewArrayInit) {
                lc._ilg.EmitArray(
                    node.Type.GetElementType(),
                    node.Expressions.Count,
                    delegate(int index) {
                        lc.EmitExpression(node.Expressions[index]);
                    }
                );
            } else {
                ReadOnlyCollection<Expression> bounds = node.Expressions;
                for (int i = 0; i < bounds.Count; i++) {
                    lc.EmitExpression(bounds[i]);
                }
                lc._ilg.EmitArray(node.Type);
            }
        }


        private static void EmitScopeExpression(LambdaCompiler lc, Expression expr) {
            ScopeExpression node = (ScopeExpression)expr;
            ScopeInfo saved = lc._scope;

            lc._scope = lc._compiler.GetScopeInfo(node);
            if (lc._scope == null) {
                lc._scope = saved;

                // we merged scope, just emit the body and be done with it
                lc.EmitExpression(node.Body);
                return;
            }

            // push scope
            lc._scope.Enter(lc);

            // emit body
            lc.EmitExpression(node.Body);

            // pop scope
            lc._scope.Exit();
            lc._scope = saved;
        }

        private static void EmitActionExpression(LambdaCompiler lc, Expression expr) {
            throw new InvalidOperationException("Action expression should have been reduced");
        }

        private static void EmitDeleteExpression(LambdaCompiler lc, Expression expr) {
            throw new InvalidOperationException("Delete expression should have been reduced");
        }

        private static void EmitExtensionExpression(LambdaCompiler lc, Expression expr) {
            throw new InvalidOperationException("Extension expression should have been reduced");
        }

        #region Expression helpers

        private void EmitExpressionAsObjectOrNull(Expression node) {
            if (node == null) {
                _ilg.Emit(OpCodes.Ldnull);
            } else {
                EmitExpressionAsObject(node);
            }
        }

        #endregion

        enum EmitAs {
            Default,
            Void
        }
    }
}
